package server;

//Import UDP 
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.io.PrintWriter;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.net.SocketException;

//Import Socket needs
import java.net.InetAddress;

//Misc
import java.nio.ByteBuffer;
import java.nio.channels.Channel;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.DatagramChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Set;

import dao.DatabaseConnection;

import obj.Piped;
import server.Oserver;

/**
 * Class Main Server instance
 */
public class ServerMain {
    
    private static ByteBuffer rBuffer = ByteBuffer.allocate(Oserver.buffsize);
    //Client soc
    private static SocketAddress clientAddress;
    private static Oserver rserver ;
    
    /**
     * Constructor Server
     * @param rserver
     */
    public ServerMain(Oserver rserver){
    	ServerMain.rserver = rserver;
        System.out.println("S : Server initialisation...");
    }

    
    
    /**
     * Add client to the list of thread
     * @param clientAddress
     * @param pipeList
     */
    public static void addTheSession(SocketAddress clientAddress , Piped pipeList){
    	rserver.getlOfThreads().put(clientAddress, pipeList);
    }
    
    /**
     * Clean the thread of MessageReader
     * @throws IOException
     */
    public static void cleanTheSession() throws IOException{
    	
    	//if the number of thread reach the limit clear all phone in memory
        if ( Oserver.getNbMobileThread() > rserver.getThisNbMax() ) {
			System.out.println("S : Reach thread limit "+ rserver.getThisNbMax() +", cleaning phone list ..." );
        	
            Set<SocketAddress> key = rserver.getlOfThreads().keySet();
            Iterator<SocketAddress> it = key.iterator();
            HashMap<SocketAddress,Piped>  newlOfThreads = (HashMap<SocketAddress, Piped>) rserver.getlOfThreads().clone() ;
            
            while (it.hasNext()){
            	
				Object obj = it.next();
				Piped tmpPipeList = rserver.getlOfThreads().get(obj);

				//if server has respond remove it from the list
				if (tmpPipeList.isResponseSent()){
					//Close the pipe
					byte[] tmpBuff = new byte[Oserver.buffsize];

					tmpBuff[0]=-1;
					tmpPipeList.getOs().write(tmpBuff);
					tmpPipeList.getOs().close();
					
					//stop the thread with a clean method
					tmpPipeList.getMreader().endState();
					newlOfThreads.remove(obj) ;
					Oserver.decNbMobileThread();
				}
            }
            rserver.setlOfThreads(newlOfThreads);
        }
    }
    
    /**
     * createPipeConnection create a new PipeInputStream/PipeOutputStream and connect them together. It save them in PipeObj oject
     * @return Piped
     */
    public static Piped createPipeConnection(){
        //Store access to threads
        //create new pipeList
        Piped pipeObj = new Piped();
        PipedOutputStream os = new PipedOutputStream();
        PipedInputStream is = new PipedInputStream();

        //Attache them together
	   	try {
			os.connect(is);
		} catch (IOException e) {
			e.printStackTrace();
			System.out.println("S : Cannot create InputStream for the client ");
		}
		
	   	//save all
        pipeObj.setIs(is);
        pipeObj.setOs(os);
        
	   	return pipeObj;
	}
    public static void closesocket(){
    	
    	try {
			rserver.getUdpchannel().close();
		} catch (IOException e) {
			e.printStackTrace();
			System.out.println("Socket not exists.");
		}
    }
    
    

    /**
     * Launch HSQLDB database
     */
    public static void launchdatabase(){
    	
    	//system.java -cp  org.hsqldb.server.Server --database.0 file: --dbname.0 db
    	//org.hsqldb.Server
    	//database.setAddress(ConServer.getInetAddress().toString());
    	//database.setPort(9191);
    	rserver.getDatabase().setDatabaseName(0, "db");
    	rserver.getDatabase().setDatabasePath(0, "wifigeo_lib/database" );//DatabaseConnection.getDbpath());
    	//database.setLogWriter(arg0);
    	rserver.getDatabase().setWebRoot("wifigeo_lib/hsqldb.jar");//DatabaseConnection.getDbjar());//
    	rserver.getDatabase().setRestartOnShutdown(true);
		File f=new File(DatabaseConnection.getDblog());
		rserver.getDatabase().setTrace(false);

    	if (!f.exists()){
    		System.out.print("Creating file...");
			try {
				f.createNewFile();
			} catch (IOException e) {
				e.printStackTrace();
			}
    	}
    	PrintWriter printlog = null;
		try {
			printlog = new PrintWriter(DatabaseConnection.getDblog());
		} catch (FileNotFoundException e) {
			e.printStackTrace();

		}
		rserver.getDatabase().setLogWriter(printlog);
		rserver.getDatabase().start();
     }
//    public Runnable getShutdownRunnable(){
//        database.stop();
//    }
    

    
    /**
     * Send packet to the server UDP socket  
     * @param DatagramChannel
     * @param SocketAddress
     * @param SocketAddress
     * @param ByteBuffer
     */
    public static void sendpacket (DatagramChannel udpchannel, SocketAddress socCliAdd,ByteBuffer rBuffer ) {

        try {
            System.out.println( "Ss : Sending " + rBuffer.capacity()+ " bytes on " + Oserver.getInetAddress().getHostAddress() + " on port "+ Oserver.getInetAddress().getAddress()+" to client : "+ ((InetSocketAddress)socCliAdd).getAddress().getHostAddress() +" to port " +  ((InetSocketAddress)socCliAdd).getPort() );

            //send ByteBuffer on the socket
            udpchannel.send(rBuffer, socCliAdd);
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("Error in sending data");
        }
    }
 
    
    /**
     * Launch the receive socket on which the server listen UDP and allow to send at the same time
     * @param server port
     * @param server InetAddress
     * @throws IOException 
     */
    public static void launchserver (int port, InetAddress hostAddress) throws IOException {
    	
    	//Create socket address
        SocketAddress localport = new InetSocketAddress(port);
    	
        // Also create and bind a DatagramChannel to listen on.
        try {
        	DatagramChannel udpchannel = DatagramChannel.open();
			rserver.setUdpchannel(udpchannel);
		} catch (IOException e1) {
			e1.printStackTrace();
		}
        
    	// create bind a DatagramChannel
        try {
			rserver.getUdpchannel().socket().bind(localport);
		} catch (SocketException e) {
			e.printStackTrace();
	        System.out.println("S : Port already in use, server close.");
			System.exit(-1);
		}

        System.out.println("S : Server listening at : "+ ((InetSocketAddress)localport).getAddress().getHostAddress()  + ", on port :" + ((InetSocketAddress)localport).getPort() );

        // Set to non blocking port
        rserver.getUdpchannel().configureBlocking(false);

        // The Selector object is what allows us to block while waiting
        // for activity on either of the two channels.
        Selector selector = Selector.open();

        // Register the channels with the selector, tell it to
        // to wake up for read estate on that port
        // when receiving UDP packet for instance
        try {
        	rserver.getUdpchannel().register(selector, SelectionKey.OP_READ);
		} catch (ClosedChannelException e) {
			e.printStackTrace();
	        System.out.println("S : Cannot register with the selector while channel is closed.");
			System.exit(-1);
		}
	

        // Now loop forever, processing client connections
        while(true) {

        	//TODO to activate thread manager uncomment the following
        	//if the max number is reach

        	
//        	cleanTheSession();
            
        	//clear the buffer before
        	rBuffer.clear();
        	
        	// If the port channel changes its status , here for read estate, the selector unlock the reading 
	    	selector.select();
	    	
	    	System.out.println("S : Socket (channel) change status to Readable");
			
	        // Get the SelectionKey objects for the channels that have
	        // activity on them. These are the keys returned by the
	        // register() methods above. They are returned in a
	        // java.util.Set.
	        Set<SelectionKey> keys = selector.selectedKeys();
	
	        // Iterate through the Set of keys.
	        //for our case there is just One : UDP
	        for (Iterator<SelectionKey> i = keys.iterator(); i.hasNext();) {
	            // Get a key from the set, and remove it from the set
	        	SelectionKey key = i.next();
	            i.remove();
	            
	            // Get the channel associated with the key
	            Channel channel = key.channel();

	            //retrieve server buffer
	            //ByteBuffer rBuffer = rserver.getrBuffer();
	            
				//Accept only UDP
	            if (key.isReadable() && channel == rserver.getUdpchannel()) {
	               // A UDP datagram is waiting. Receive it now,
	               // noting the address it was sent from.
	            	
	            	try {
	            		//receive a byteBuffer
	            		clientAddress = rserver.getUdpchannel().receive(rBuffer);

					} catch (IOException e) {
						e.printStackTrace();
						System.out.println("Error while receiving data.");
					}
	            }else
					System.out.println("Server does not support tcp yet ! Sorry...");
	            
//	            Set<SocketAddress> cles = rserver.getlOfThreads().keySet();
//	            Iterator<SocketAddress> it = cles.iterator();
//	            boolean isNewPhone = true;
//	            
//	            //At each thread see if the current phone is already registered
//	            while (it.hasNext()){
//	               Object obj = it.next();
//
//	               //create a pipe object to retrieve the outputstream
////	               Piped tmpPipe =  rserver.getlOfThreads().get(obj);
//
//	               //if client exist the buffer is sent to the outputstream to the thread 
//	               if (obj == clientAddress){
//						System.out.println("S : Server has received a packet from an old client");
//
//						//send to the thread listening this packet
//						tmpPipe.getOs().write(rBuffer.array(),0,rBuffer.capacity());
//						//end the loop
//						isNewPhone=false;
//						break;
//	               }
//	            }
	            
//	            if (isNewPhone){
					System.out.println("S : New phone is asking for positionning.");
		        
					Piped pipeObj = new Piped(); 
					pipeObj = createPipeConnection();
//					for (int i1 = 1 ; i1< 30; i1++ )  {
						//creating a new instance for this phone
			            MessageReader mreader = new MessageReader(rserver.getUdpchannel(), clientAddress, rBuffer.array(), pipeObj, 32 );

		        	   	pipeObj.setMreader(mreader);

		        	   	//save the current pipeinstream,pipeoutstream,buffer session in the list of thread
		        	   	addTheSession(clientAddress, pipeObj);

			    		//Create new thread MessageReader thread
				    	Thread child = new Thread(mreader);

				    	//Launch the child
				    	child.start();
//					}
//	            }
	        }
        }
    }

    /**
     * Launch the server 
     */
    public static void main(String[] args) {
	    Runtime.getRuntime().addShutdownHook(new Thread(){
		    public void run() {
	    		ServerMain.closesocket();
		    	System.out.println("Close") ;
		  }
		 });
    }
//    	Oserver rserver  = new Oserver();
//    	new ServerMain(rserver);
//        launchdatabase();
//		try {
//			launchserver(Oserver.getPort(), Oserver.getInetAddress());
//		} catch (IOException e) {
//			e.printStackTrace();
//            System.out.println("Relancer le server !");
//            System.exit(-1);
//		}
//        rserver.getDatabase().stop();
//        //Runtime.getRuntime().addShutdownHook(mreader);
//        //Runtime.getRuntime().runFinalization();
//    }
}